﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using snat.model;

namespace snat.algorithms.network.TFIDF {
    public class Document {

        public readonly int ID;
        public readonly DateTime Date;
        public readonly List<ClusterNode> Recipients;
        public readonly ClusterNode Sender;
        public Dictionary<Term, double> tf { get; private set; }
        public Dictionary<Term, double> tfidf { get; private set; }
        public double DocumentImportance {get; private set;}
        public int WordCount { get; private set; }

        public Document(List<Document> documents) {
            tf = new Dictionary<Term, double>();
            Dictionary<Term, int> termCounts = new Dictionary<Term, int>();
            foreach (Document d in documents) {
                foreach (Term t in d.tf.Keys) {
                    if (termCounts.ContainsKey(t)) {
                        termCounts[t]+=(int)(d.tf[t]*d.WordCount);
                    } else {
                        termCounts.Add(t, (int)(d.tf[t]*d.WordCount));
                    }
                }
                WordCount += d.WordCount;
            }
            foreach (Term t in termCounts.Keys) {
                tf.Add(t, (double)termCounts[t] / (double)WordCount);
            }
        }

        public bool Populated {
            get {
                return tfidf != null;
            }
        }

        public virtual String DocumentType {
            get {
                return String.Empty;
            }
        }

        public Document(Message m, ref Dictionary<String, Term> terms, bool updateTerms) {
            this.ID = m.ID;
            this.Date = m.Timestamp;
            this.Sender = NetworkModel.Instance.Nodes[m.Sender.ID] as ClusterNode;
            this.Recipients = m.Recipients.ConvertAll<ClusterNode>(e => NetworkModel.Instance.Nodes[e.ID] as ClusterNode);
            this.tf = new Dictionary<Term, double>();
        }        

        public void PopulateTFIDF(Dictionary<Term, double> idf) {
            tfidf = new Dictionary<Term, double>();
            foreach (Term t in tf.Keys) {
                tfidf.Add(t, tf[t] * idf[t]);
            }
            double norm = Math.Sqrt(tfidf.Values.Sum(e => e * e));
            foreach (Term t in tf.Keys) {
                tfidf[t] /= norm;
            }
            DocumentImportance = tfidf.Values.Sum();
        }

        public double CosineSimilarity(Document other) {
            if (!this.Populated || !other.Populated) {
                throw new InvalidOperationException("The TFIDF Vectors are not yet populated, this: "+this.Populated+", other: "+other.Populated);
            }
            double top = 0;
            double thisSumSq = 0;
            double otherSumSq = 0;
            foreach (Term t in this.tfidf.Keys) {   
                if (other.tfidf.ContainsKey(t)) {
                    top += this.tfidf[t] * other.tfidf[t];
                    thisSumSq += this.tfidf[t] * this.tfidf[t];
                    otherSumSq += other.tfidf[t] * other.tfidf[t];
                }
            }
            double bottom = Math.Sqrt(thisSumSq) + Math.Sqrt(otherSumSq);
            //return SigmoidFunction(top / bottom);
            return top / bottom;
        }

        public double JaccardIndex(Document other) {
            double intersectionMag = this.tf.Keys.Intersect(other.tf.Keys).Count();
            double unionMag = this.tf.Keys.Union(other.tf.Keys).Count();
            return intersectionMag / unionMag;
        }

        public double SigmoidFunction(double d) {
            return 1.0 / (1.0 + Math.Exp(-d));
        }

        public override String ToString() {
            StringBuilder ret = new StringBuilder();
            foreach (Term t in tf.Keys.OrderBy(e => e.word)) {
                ret.Append(t.word);
                ret.Append(",");
            }
            return ret.ToString();
        }
    }
}
